"""Test :class:`sievemgr.BaseShell`."""

#
# Copyright 2024  Odin Kroeger
#
# This file is part of SieveManager.
#
# SieveManager is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
#
# SieveManager is distributed in the hope that it will be useful,
# but WITHOUT ALL WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with SieveManager. If not, see <https://www.gnu.org/licenses/>.
#

# pylint: disable=missing-class-docstring,missing-function-docstring


#
# Modules
#

import io
import os
import pathlib
import shutil
import sys
import unittest

from typing import Final, Sequence
from unittest.mock import patch

sys.path.append(os.path.realpath(pathlib.Path(__file__).parents[1]))

from sievemgr import BaseShell, ShellWord
from . import Test, makerunner


#
# Globals
#

LINES: Final[tuple[Test[list[ShellWord]], ...]] = (
    # Empty
    (('',), []),
    ((' ',), []),
    (('        ',), []),
    (('\t',), []),
    (('\t\n     ',), []),
    (('#',), []),
    (('# foo',), []),
    (('    # foo',), []),
    (('\t\n    # foo\\\nfoo',), []),
)


WORDS: Final[tuple[Test[str], ...]] = (
    (((), 0), ''),
    ((('',), 0), '\n'),
    (((' ',), 0), '\n'),
    (((), 1), ''),
    ((('',), 1), '\n'),
    (((' ',), 1), '\n'),
    (((), 2), ''),
    ((('',), 2), '\n'),
    (((' ',), 2), '\n'),
    ((('foo',), 3), 'foo\n'),
    ((('foo', 'bar'), 3), 'foo\nbar\n'),
    ((('foo', 'bar'), 4), 'foo\nbar\n'),
    ((('foo', 'bar'), 6), 'foo\nbar\n'),
    ((('foo', 'bar'), 7), 'foo bar\n')
)


#
# Classes
#

class MockReadline():
    def __init__(self, line: str):
        for func in self.functions:
            self.mockups[func] = patch(func, autospec=True)
        self.line = line

    def __enter__(self) -> 'MockReadline':
        ctx = {func.removeprefix('readline.'): mockup.__enter__()
               for func, mockup in self.mockups.items()}
        ctx['get_begidx'].return_value = 0
        ctx['get_line_buffer'].return_vaue = self.line
        return self

    def __exit__(self, exctype, excvalue, traceback):
        for mockup in self.mockups.values():
            mockup.__exit__(exctype, excvalue, traceback)

    functions = ('readline.get_begidx', 'readline.get_line_buffer')
    mockups: dict = {}


#
# Wrappers
#

def columnise(words: Sequence[str],
              width: int = shutil.get_terminal_size().columns) -> str:
    buf = io.StringIO()
    BaseShell.columnise(words, file=buf, width=width)
    return buf.getvalue()


def getargs(line: str) -> list[ShellWord]:
    with MockReadline(line=line):
        return BaseShell.getargs()


#
# Tests
#

class TestBaseShell(unittest.TestCase):
    def setUp(self):
        class TestShell(self.basecls):  # type: ignore[name-defined]
            def do_foo(self):
                pass

            def do_bar(self):
                pass

            def do_(self):
                pass

        self.shellcls = TestShell

    def test_init(self):
        base = self.basecls()
        assert {'exit', 'help'} <= set(base.commands)
        sh = self.shellcls()
        self.assertEqual(sh.commands, ('bar', 'exit', 'foo', 'help'))

    test_columnise = makerunner(columnise, WORDS)

    test_getargs = makerunner(getargs, LINES)

    basecls: type[BaseShell] = BaseShell


# test if aliases are resolved
# test if help messages are generated correctly.
# test completion, that will be hardest... requires mocking up readline


#
# Boilerplate
#

if __name__ == '__main__':
    unittest.main()
